package com.esdk.utils;

import java.io.Serializable;
import java.util.*;
import java.util.List;

import com.esdk.exception.SdkRuntimeException;
import com.esdk.interfaces.Func;
import lombok.NonNull;

import com.esdk.esdk;

import redis.clients.jedis.*;
import redis.clients.jedis.params.GeoAddParams;
import redis.clients.jedis.params.GeoRadiusParam;

public class RedisUtils{
	private static JedisPool jedisPool;

	static {
		init();
	}

	public static void init(){
		JedisPoolConfig config=new JedisPoolConfig();
		config.setTestOnBorrow(false);
		String redisHost=esdk.prop.getString("redis.host");
		int redisTimeout=esdk.prop.getInteger("redis.timeout",Protocol.DEFAULT_TIMEOUT);
		String redisPassword=esdk.prop.getString("redis.password");
		int maxConection=esdk.prop.getInteger("redis.maxTotal",100);
		config.setMaxTotal(maxConection);// 默认100个连接实例，如果没有释放就会卡住。
		esdk.sout("Jedis host:{}, database:{}, maxTotal:{}",redisHost,esdk.prop.getInteger("redis.database"),config.getMaxTotal());
		config.setBlockWhenExhausted(false);
		jedisPool=new JedisPool(config,redisHost,esdk.prop.getInteger("redis.port"),redisTimeout,redisPassword);
	}

	public static JedisPool getRedisPool() {
		if(jedisPool.isClosed())
			init();
		return jedisPool;
	}

	public static RedisClient getRedisClient() {
		return new RedisClient();
	}

	/*注意一定在尾部带*号，否则没有东西返回*/
	public static String[] keys(String pattern) {
		try(RedisClient client=getRedisClient()){
			if(!pattern.endsWith("*"))
				pattern=pattern+"*";
			String[] result=client.getRedis().keys(pattern).toArray(new String[0]);
			return result;
		}
	}

	public static Long del(String key) {
		try(RedisClient client=getRedisClient()){
			Long result=client.del(key);
//		esdk.sout("redis del key["+key+"]");
			return result;
		}
	}

	public static Long unlink(String key) {
		try(RedisClient client=getRedisClient()){
			Long result=client.unlink(key);
//		esdk.sout("redis del key["+key+"]");
			return result;
		}
	}

	public static Long del(String... keys) {
		Long result=0L;
		if(keys!=null&&keys.length>0) {
			try(RedisClient client=getRedisClient()){
				result=client.del(keys);
//			esdk.sout("redis del keys["+sdk.str.valueOf(keys)+"]");
			}
		}
		return result;
	}

	public static Long unlink(String... keys) {
		Long result=0L;
		if(keys!=null&&keys.length>0) {
			try(RedisClient client=getRedisClient()){
				result=client.unlink(keys);
//			esdk.sout("redis del keys["+sdk.str.valueOf(keys)+"]");
			}
		}
		return result;
	}

	public static Long delKeys(String key) {
		try(RedisClient client=getRedisClient()){
			Long result=client.delKeys(key);
			return result;
		}
	}

	/**强制转为字符串保存*/
	public static void set(String key,Object value) {
		set(key,value+"");
	}

	public static void set(String key,String value) {
		try(RedisClient client=getRedisClient()){
			client.set(key,value);
		}
	}

	/**
	 * Redis Setnx（SET if Not eXists） 命令在指定的 key 不存在时，为 key 设置指定的值。
	 * @return 设置成功返回1，设置失败返回0
	 * */
	public static boolean setnx(String key,Object value) {
		try(RedisClient client=getRedisClient()){
			boolean result=client.setnx(key,value);
			return result;
		}
	}

	/**
	 * Redis Setnx（SET if Not eXists） 命令在指定的 key 不存在时，为 key 设置指定的值。
	 * @param sec 设置过期秒数
	 * @return 设置成功返回1，设置失败返回0
	 * */
	public static boolean setnx(String key,Object value,int sec) {
		try(RedisClient client=getRedisClient()){
			boolean result=client.setnx(key,value,sec);
			return result;
		}
	}

	public static void set(String key,Object value,int sec) {
		set(key,value+"",sec);
	}

	public static void set(String key,String value,int sec){
		try(RedisClient client=getRedisClient()){
			client.set(key,value,sec);
//		esdk.sout("redis set key["+key+"]("+sec+"秒)="+value);
		}
	}

	/**以秒为单位返回 key 的剩余过期时间，返回-1表示没有过期时间，返回-2表示没有该key*/
	public static Long ttl(String key) {
		try(RedisClient client=getRedisClient()){
			Long result=client.ttl(key);
			esdk.sout("redis del key["+key+"]");
			return result;
		}
	}

	/**注意value不能有逗号*/
	public static boolean contains(String key,Object value){
		try(RedisClient client=getRedisClient()){
			return client.contains(key,value);
		}
	}

	/**检查给定 key 是否存在*/
	public static boolean exists(String key) {
		try(RedisClient client=getRedisClient()){
			return client.exists(key);
		}
	}
	/**如果 KEY 存在，返回 1，否则，返回 0。用于判断多个 KEY 时，返回存在的 KEY 的数目。*/
	public static long exists(String... key) {
		try(RedisClient client=getRedisClient()){
			long result=client.exists(key);
			return result;
		}
	}

	public static long incr(String key) {
		try(RedisClient client=getRedisClient()){
			return client.incr(key);
		}
	}


	/**
	 * 将 key 中储存的数字值增一。
	 * 如果 key 不存在，那么 key 的值会先被初始化为 0 ，然后再执行 INCR 操作。
	 * 如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误。
	 */
	public static long incr(String key,int sec) {
		try(RedisClient client=getRedisClient()){
			long result=client.incr(key,sec);
			return result;
		}
	}

	public static long decr(String key) {
		try(RedisClient client=getRedisClient()){
			long result=client.decr(key);
			return result;
		}
	}

	/**
	 * 将 key 中储存的数字值减一。
	 * 如果 key 不存在，那么 key 的值会先被初始化为 0 ，然后再执行 DECR 操作。
	 * 如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误。
	 * 本操作的值限制在 64 位(bit)有符号数字表示之内。
	 */
	public static long decr(String key,int sec) {
		try(RedisClient client=getRedisClient()){
			long result=client.decr(key,sec);
			return result;
		}
	}

	/**序列化对象*/
	public static <T extends Serializable> T setObj(String key,T value,int sec) {
		try(RedisClient client=getRedisClient()){
			client.setObj(key,value,sec);
			return value;
		}
	}

	/**序列化对象*/
	public static <T extends Serializable> T setObj(String key,T value) {
		try(RedisClient client=getRedisClient()){
			client.setObj(key,value);
			return value;
		}
	}

	/**反序列化对象*/
	public static Object getObj(String key) {
		try(RedisClient client=getRedisClient()){
			Object result=client.getObj(key);
			return result;
		}
	}

	/**反序列化对象*/
	public static Object getObj(String key,int sec) {
		try(RedisClient client=getRedisClient()){
			Object result=client.getObj(key,sec);
			return result;
		}
	}

	/**设置key过期时间*/
	public static void expire(String key,int sec) {
		try(RedisClient client=getRedisClient()){
			client.expire(key,sec);
		}
	}

	public static String get(String key) {
		try(RedisClient client=getRedisClient()){
			String result=client.get(key);
			return result;
		}
	}

	public static String get(String key,int sec) {
		try(RedisClient client=getRedisClient()){
			String result=client.get(key,sec);
			return result;
		}
	}

	public static void hmset(String key,String... keyValues) {
		try(RedisClient client=getRedisClient()){
			client.hmset(key,keyValues);
		}
	}

	public static String hmset(String key,Map map) {
		try(RedisClient client=getRedisClient()){
			return client.hmset(key,map);
		}
	}
	/**
	 * 用于同时将多个 field-value (字段-值)对设置到哈希表中。
	 * 此命令会覆盖哈希表中已存在的字段。
	 * 如果哈希表不存在，会创建一个空哈希表，并执行 HMSET 操作。
	 * */
	public static void hmset(String key,Map map,int sec) {
		try(RedisClient client=getRedisClient()){
			client.hmset(key,map);
			client.expire(key,sec);
		}
	}

	/**
	 * 用于返回哈希表中，一个或多个给定字段的值。
	 * 如果指定的字段不存在于哈希表，那么返回一个 nil 值。
	 * */
	public static List<String> hmget(String key,String... fields) {
		try(RedisClient client=getRedisClient()){
			List<String> result=client.hmget(key,fields);
			return result;
		}
	}

	/**用于为哈希表中的字段赋值 。
	 如果哈希表不存在，一个新的哈希表被创建并进行 HSET 操作。
	 如果字段已经存在于哈希表中，旧值将被覆盖。*/
	public static Long hset(String key,String field,String value,int sec) {
		try(RedisClient client=getRedisClient()){
			Long result=client.hset(key,field,value,sec);
			return result;
		}
	}

	public static Long hset(String key,String field,String value) {
		try(RedisClient client=getRedisClient()){
			Long result=client.hset(key,field,value);
			return result;
		}
	}

	public static Long hincrBy(String key, String field, long incrValue){
		try(RedisClient client=getRedisClient()){
			Long result=client.hincrBy(key, field, incrValue);
			return result;
		}
	}

	public static Double hincrBy(String key, String field, double incrValue){
		try(RedisClient client=getRedisClient()){
			Double result=client.hincrByFloat(key, field, incrValue);
			return result;
		}
	}

	public static Long hincrByIfExisted(@NonNull String key,@NonNull String field,long incrValue){
		try(RedisClient client=getRedisClient()){
			Object result = client.getRedis().eval("""
				if redis.call('HEXISTS', KEYS[1], ARGV[1]) == 0 then
				  return nil
				end
				return redis.call('HINCRBY', KEYS[1], ARGV[1], tonumber(ARGV[2]))
				""",1,key,field,String.valueOf(incrValue));
			return esdk.obj.valueOf(Long.class, result);
		}
	}

	public static Double hincrByIfExisted(@NonNull String key,@NonNull String field,double incrValue){
		try(RedisClient client=getRedisClient()){
			Object result = client.getRedis().eval("""
				if redis.call('HEXISTS', KEYS[1], ARGV[1]) == 0 then
				  return nil
				end
				return redis.call('HINCRBYFLOAT', KEYS[1], ARGV[1], tonumber(ARGV[2]))
				""",1,key,field,String.valueOf(incrValue));
			return esdk.obj.valueOf(Double.class, result);
		}
	}

	public static Double zincrByIfExisted(@NonNull String key,@NonNull String field,double incrValue){
		try(RedisClient client=getRedisClient()){
			Object result = client.getRedis().eval("""
				if not redis.call('ZSCORE', KEYS[1], ARGV[1]) then
				  return nil
				end
				return redis.call('ZINCRBY', KEYS[1], tonumber(ARGV[2]), ARGV[1])
				""",1,key,field,String.valueOf(incrValue));
			return esdk.obj.valueOf(Double.class, result);
		}
	}

	public static boolean hexists(String key, String field){
		try(RedisClient client=getRedisClient()){
			return client.hexists(key, field);
		}
	}

	/**返回哈希表中指定字段的值。*/
	public static String hget(String key,String field) {
		try(RedisClient client=getRedisClient()){
			String result=client.hget(key,field);
			return result;
		}
	}

	public static Long hsetObj(String key,String field,Object value,int sec) {
		try(RedisClient client=getRedisClient()){
			Long result=client.hsetObj(key,field,value,sec);
			return result;
		}
	}

	public static Object hgetObj(String key,String field) {
		try(RedisClient client=getRedisClient()){
			Object result=client.hgetObj(key,field);
			return result;
		}
	}
	/**
	 * 使用场景：点赞、签到、打卡
	 * 参考：https://mp.weixin.qq.com/s/so23ArKtMVZJHnP6ajF4Ag
	 * 将一个或多个成员元素加入到集合中，已经存在于集合的成员元素将被忽略。
	 * 假如集合 key 不存在，则创建一个只包含添加的元素作成员的集合。
	 * 当集合 key 不是集合类型时，返回一个错误。
	 */
	public static long sadd(String key,Object value) {
		try(RedisClient client=getRedisClient()){
			long result=client.sadd(key,value);
			return result;
		}
	}
	/**
	 * 用于移除集合中的一个或多个成员元素，不存在的成员元素会被忽略。
	 * 当 key 不是集合类型，返回一个错误。
	 * */
	public static long srem(String key,Object value) {
		try(RedisClient client=getRedisClient()){
			long result=client.srem(key,value);
			return result;
		}
	}
	/**
	 * 判断成员元素是否是集合的成员。
	 * */
	public static boolean sismember(String key,Object value) {
		try(RedisClient client=getRedisClient()){
			boolean result=client.sismember(key,value);
			return result;
		}
	}
	/**返回集合中的所有的成员。 不存在的集合 key 被视为空集合*/
	public static Set<String> smembers(String key) {
		try(RedisClient client=getRedisClient()){
			Set<String> result=client.smembers(key);
			return result;
		}
	}
	/**返回集合中元素的数量*/
	public static long scard(String key) {
		try(RedisClient client=getRedisClient()){
			long result=client.scard(key);
			return result;
		}
	}
	/**
	 * 返回第一个集合与其他集合之间的差异，也可以认为说第一个集合中独有的元素。不存在的集合 key 将视为空集。
	 * 差集的结果来自前面的 FIRST_KEY ,而不是后面的 OTHER_KEY1，也不是整个 FIRST_KEY OTHER_KEY1..OTHER_KEYN 的差集。
	 * 实例:
	 * key1 = {a,b,c,d}
	 * key2 = {c}
	 * key3 = {a,c,e}
	 * SDIFF key1 key2 key3 = {b,d}
	 * */
	public static Set<String> sdiff(String key) {
		try(RedisClient client=getRedisClient()){
			Set<String> result=client.sdiff(key);
			return result;
		}
	}
	/**
	 * 返回给定所有给定集合的交集。 不存在的集合 key 被视为空集。 当给定集合当中有一个空集时，结果也为空集(根据集合运算定律)。
	 * */
	public static Set<String> sinter(String key) {
		try(RedisClient client=getRedisClient()){
			Set<String> result=client.sinter(key);
			return result;
		}
	}

	/**移出并获取列表的第一个元素， 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止*/
	public static List<String> blpop(int timeoutSec,String key){
		try(RedisClient client=getRedisClient()){
			List<String> result=client.blpop(timeoutSec,key);
			return result;
		}
	}

	/**移出并获取列表的最后一个元素， 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止*/
	public static List<String> brpop(String key,int timeoutSec){
		try(RedisClient client=getRedisClient()){
			List<String> result=client.brpop(timeoutSec,key);
			return result;
		}
	}

	public static <R> R process(Func<Jedis,R> func){
		try(var redisClient = RedisUtils.getRedisClient()){
			return func.invoke(redisClient.getRedis());
		}catch(Exception e){
      throw new SdkRuntimeException(e);
    }
  }

	/**
	 * 添加单个 GEO/覆盖 坐标
	 */
	public static Long geoAdd(String key,double longitude,double latitude,String member) {
		try(RedisClient client=getRedisClient()){
			return client.getRedis().geoadd(key,longitude,latitude,member);
		}
	}

	/**
	 * 批量添加/覆盖 GEO 坐标
	 *
	 * @param key Redis key
	 * @param map 成员 -> 坐标映射
	 */
	public static Long geoAdd(String key,Map<String, GeoCoordinate> map) {
		try(RedisClient client=getRedisClient()){
			return 	client.getRedis().geoadd(key,map);
		}
	}

	/**
	 * 带参数批量添加/覆盖（如 NX/XX/CH 等）
	 *
	 * @param key                 Redis key
	 * @param params              GeoAddParams 参数对象
	 * @param map 成员 -> 坐标映射
	 * @return 成功添加数量
	 */
	public static Long geoAdd(String key,GeoAddParams params,Map<String, GeoCoordinate> map) {
		try(RedisClient client=getRedisClient()){
			return client.getRedis().geoadd(key, params, map);
		}
	}

	/**
	 * 删除geo
	 * @param key
	 * @param members
	 * @return
	 */
	public static Long geoDel(String key, String... members) {
		try(RedisClient client=getRedisClient()){
			return client.getRedis().zrem(key, members);
		}
	}

	/**
	 * 添加单个 GEO/覆盖 坐标
	 */
	public static List<GeoRadiusResponse> geoSearch(String key,double longitude,double latitude,double radius,GeoUnit unit) {
		try(RedisClient client=getRedisClient()){
			GeoRadiusParam param = GeoRadiusParam.geoRadiusParam()
				.withDist()        // 返回距离
				.withCoord()       // 返回经纬度
				.sortAscending();   // 按距离升序
			return client.getRedis().georadius(key,longitude,latitude,radius,unit,param);
		}
	}


	public static void main(String[] args) throws Exception{
		System.out.println(zincrByIfExisted("DUTY:POOL", "500", 1));
		System.out.println(zincrByIfExisted("DUTY:POOL", "100", 1));
		String key="测试KEY";
		RedisUtils.set(key,"ABcd1234我们是技术部",60);
		String[] keys=RedisUtils.keys("*");
		esdk.sout(keys.length);
		esdk.tool.assertEquals((Long)RedisUtils.del(keys)>=1);
		esdk.tool.assertEquals(keys[0],key);
		keys=RedisUtils.keys("TEST *");
		esdk.tool.assertEquals(keys.length,0);
		keys=RedisUtils.keys("SELECT *");
		esdk.sout(RedisUtils.del(keys));

		String lockKey="stock_lock";
		boolean setnx=RedisUtils.setnx(lockKey,"12345",1);
		esdk.tool.assertEquals(setnx==true);
		boolean setnx1=RedisUtils.setnx(lockKey,"12345",1);
		esdk.tool.assertEquals(setnx1==false);
		esdk.tool.assertEquals(RedisUtils.get(lockKey),"12345");
		Thread.sleep(1000);
		esdk.tool.assertEquals(RedisUtils.get(lockKey)==null);

		RedisUtils.setnx("db_global_seq",esdk.prop.getInteger("initSeqVal",10000000));
		RedisUtils.setnx("db_global_seq",1000);
		long seq=RedisUtils.incr("db_global_seq");
		esdk.tool.assertEquals(seq,10000001);
		RedisUtils.delKeys("*");
		RedisUtils.set("测试key","测试内容");
		long ret=RedisUtils.del("测试key");
		esdk.tool.assertEquals(ret,1);
		esdk.tool.assertEquals(RedisUtils.get("测试key")==null);
	}
}
